import java.util.Random;
import java.util.concurrent.Semaphore;

public class Main {
    // 1. 定义实验常量（与文档一致）

    private static final int BUFFER_SIZE = 5;                                                   // 缓冲区容量
    private static final int PRODUCER_NUM = 2;                                                  // 生产者线程数
    private static final int CONSUMER_NUM = 3;                                                  // 消费者线程数
    private static final int PRODUCERRUN_TIME = PRODUCER_NUM * CONSUMER_NUM;                         // 生产者线程运行次数
    private static final int CONSUMERRUN_TIME = PRODUCERRUN_TIME * PRODUCER_NUM / CONSUMER_NUM;           // 消费者线程运行次数


    // 2. 共享资源：循环缓冲区、读写指针
    private static int[] buffer = new int[BUFFER_SIZE];
    private static int in = 0;  // 生产者写入指针（循环更新）
    private static int out = 0; // 消费者读取指针（循环更新）

    // 3. 信号量定义（对应文档中的3个信号量）
    // empty：同步信号量，记录缓冲区空位数量，初始值=缓冲区容量
    private static Semaphore empty = new Semaphore(BUFFER_SIZE);
    // full：同步信号量，记录缓冲区数据数量，初始值=0
    private static Semaphore full = new Semaphore(0);
    // mutex：互斥信号量，保护缓冲区访问，初始值=1
    private static Semaphore mutex = new Semaphore(1);

    public static void main(String[] args) {
        // 4. 创建并启动生产者线程
        for (int i = 1; i <= PRODUCER_NUM; i++) {
            new Thread(new Producer(i), "生产者" + i).start();
        }

        // 5. 创建并启动消费者线程
        for (int i = 1; i <= CONSUMER_NUM; i++) {
            new Thread(new Consumer(i), "消费者" + i).start();
        }
        return;
    }

    // 6. 生产者线程类（实现Runnable接口）
    static class Producer implements Runnable {
        private int producerId; // 生产者ID（区分不同生产者）
        private Random random = new Random(); // 生成随机数据

        public Producer(int producerId) {
            this.producerId = producerId;
        }

        @Override
        public void run() {
            for (int i = 0; i < PRODUCERRUN_TIME; i++) { // 循环生产（模拟持续生产）
                try {
                    // 步骤1：模拟生产数据（1~100的随机整数）
                    int data = random.nextInt(100) + 1;
                    System.out.printf("生产者%d 生成数据：%d%n", producerId, data);

                    // 步骤2：申请缓冲区空位（同步控制，对应wait(empty)）
                    empty.acquire();
                    // 步骤3：申请临界区访问（互斥控制，对应wait(mutex)）
                    mutex.acquire();

                    // 步骤4：写入数据到缓冲区（临界区操作）
                    buffer[in] = data;
                    System.out.printf("生产者%d 写入缓冲区位置%d：%d%n", producerId, in, data);
                    in = (in + 1) % BUFFER_SIZE; // 循环更新写入指针

                    // 步骤5：释放临界区（对应signal(mutex)）
                    mutex.release();
                    // 步骤6：通知消费者有新数据（对应signal(full)）
                    full.release();

                    // 步骤7：模拟生产耗时（1~2秒）
                    Thread.sleep(random.nextInt(2) + 1 * 1000);

                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
    
    // 7. 消费者线程类（实现Runnable接口）
    static class Consumer implements Runnable {
        private int consumerId; // 消费者ID（区分不同消费者）
        private Random random = new Random();

        public Consumer(int consumerId) {
            this.consumerId = consumerId;
        }

        @Override
        public void run() {
            for (int i = 0; i < CONSUMERRUN_TIME; i++) { // 循环消费（模拟持续消费）
                try {
                    // 步骤1：申请读取数据（同步控制，对应wait(full)）
                    full.acquire();
                    // 步骤2：申请临界区访问（互斥控制，对应wait(mutex)）
                    mutex.acquire();

                    // 步骤3：从缓冲区读取数据（临界区操作）
                    int data = buffer[out];
                    System.out.printf("消费者%d 从缓冲区位置%d 读取数据：%d%n", consumerId, out, data);
                    out = (out + 1) % BUFFER_SIZE; // 循环更新读取指针

                    // 步骤4：释放临界区（对应signal(mutex)）
                    mutex.release();
                    // 步骤5：通知生产者有空位（对应signal(empty)）
                    empty.release();

                    // 步骤6：模拟消费耗时（1~3秒）
                    Thread.sleep(random.nextInt(3) + 1 * 1000);

                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
}